#ifndef INC_PROC_H
#define INC_PROC_H

#include <stddef.h>

#include "arm.h"
#include "trap.h"
#include "spinlock.h"
#include "list.h"
#include "cpu_affinity.h"

#define NCPU   4        /* maximum number of CPUs */
#define NPROC 64        /* maximum number of processes */
#define NOFILE 16       /* open files per process */
#define KSTACKSIZE 4096 /* size of per-process kernel stack */

#define thiscpu (&cpus[arm_cpuid()])

/*
 * Saved registers for kernel context switches.
 * Don't need to save X1-X15 since accorrding to
 * the x86 convention it is the caller to save them.
 * Contexts are stored at the top of the stack they describe,
 * the stack pointer is the address of the context.
 * The layout of the context matches the layout of the stack in swtch.S
 */
struct context {
    uint64_t lr;
    uint64_t fp;   /* x30, x29 */
    uint64_t x28;
    uint64_t x27;
    uint64_t x26;
    uint64_t x25;
    uint64_t x24;
    uint64_t x23;
    uint64_t x22;
    uint64_t x21;
    uint64_t x20;
    uint64_t x19;
    uint64_t pr;
    uint64_t ip1;  /* x18, x17 */
    uint64_t ip0;
    uint64_t sp;   /* x16, sp_el1 */
};

struct cpu {
    struct context scheduler;  /* swtch() here to enter scheduler */
    struct proc *proc;          /* The process running on this cpu or null */
};

extern struct cpu cpus[NCPU];

enum procstate { UNUSED, EMBRYO, SLEEPING, RUNNABLE, RUNNING, ZOMBIE };

struct proc {
    list_node_t pnode;       /* List node for each process              */
    struct spinlock lock;    /* Lock for each process                   */
    uint64_t sz;             /* Size of process memory (bytes)          */
    uint64_t *pgdir;         /* Page table                              */
    char *kstack;            /* Bottom of kernel stack for this process */
    enum procstate state;    /* Process state                           */
    int pid;                 /* Process ID                              */
    struct proc *parent;     /* Parent process                          */
    struct trapframe *tf;    /* Trapframe for current syscall           */
    struct context *context; /* swtch() here to run process             */
    void *chan;              /* If non-zero, sleeping on chan           */
    int killed;              /* If non-zero, have been killed           */
    char name[16];           /* Process name (debugging)                */
    cpu_affinity_t affinity; /* Affinity for CPUs                       */

    struct file *ofile[NOFILE];  /* Open files */
    struct inode *cwd;           /* Current directory */
};

static inline struct proc *
thisproc()
{
    return thiscpu->proc;
}

typedef struct {
    uint64_t *pgdir;
    char *stack;  // stack bottom
    uint64_t size;
} pvm_info_t;

void pvm_setup(char *data, int size, pvm_info_t *info);
int pvm_create_stack(uint64_t *pgdir, uint64_t vm_size, uint64_t *sp, uint64_t *vsp);

void proc_init();
void user_init();
void scheduler();
void try_yield();

void yield();
void exit();
int fork();
int wait();
void sleep(void *chan, struct spinlock *lk);
void wakeup(void *chan);

int getpid();

void procdump();
int execve(const char *path, int argc, char *const argv[], int envc, char *const envp[]);

#endif
